# Install required libraries
packages <- c('leaflet','dplyr','data.table','sp', 'rgeos', 'raster', 
'rgdal','GISTools','magrittr','BSDA', 'PASWR','broom','tidyverse','gtools')

for(p in packages){
  if(!require(p,character.only = T)){
    install.packages(p)
  }
  library(p,character.only = T)
}

'%!in%' <- function(x,y)!('%in%'(x,y))
# Install required libraries
data <- read.csv("data/realis2018.csv")

Problem Description

One day, your mum tells you that we just won the first prize of TOTO which worth 2,500,000 SGD. After you discuss with your family, you decide to buy a flat and plan for the investment. During the period of time, you contact the company “Property Master@” to discuss more on the historical transaction of Singapore property. The sample data givenis “realis2018.csv”. For problems stated below, we use α= 5%.

# filter no. of units = 1 cos some transactions are the whole damn building
data %<>% filter(No..of.Units==1)

# recode YISHUN and Yishun
data$Planning.Area <- recode(data$Planning.Area,"YISHUN"="Yishun")

Problem 1

You look around few different planning areas (Column R). When you ask the agent what is the mean Unit price(psm) for Newton flats (Column G), she claims that the mean is higher than 26500. Do you agree with your agent’s suggestion? Explain and justify your answer.

Assumptions:

  • Assume “Flat” means either Condominium, Executive Condominium or Apartment
  • Data is a sample of housing prices in 2018

Code

Newton <-  data %>%  filter(Planning.Area=="Newton",Property.Type %in% c("Condominium", "Apartment", "Executive Condominium"), No..of.Units == "1")
z.test(Newton$Unit.Price....psm, alternative = "greater", mu= 26500,sigma.x=sd(Newton$Unit.Price....psm))

    One-sample z-Test

data:  Newton$Unit.Price....psm
z = 1.881, p-value = 0.02998
alternative hypothesis: true mean is greater than 26500
95 percent confidence interval:
 26598.75      Inf
sample estimates:
mean of x 
 27286.51 

Detailed Analysis

At 95% Confidence level, we have sufficient evidence to say that the mean price per square meter(psm) of flats in the Newton Area is more than $26500.

Problem 2

Your friend told you that Newton planning area may not be the best area to choose. He suggested you to consider other planning areas. This is a very difficult decision since you need to conduct a more comprehensive analysis and you also need to justify whether you still choose Newton or another planning area.

Assumptions:

  • Assume no preference for planning area
  • Assume purchasing in Q1 2020
  • The budget is 2.5 million
  • Assume “Flat” means either Condominium, Executive Condominium or Apartment
  • Key factors to consider: Accesibility, Estate Maturity
  • Metric for evaluation is PSM

Code

Distribution of Properties <= 2.5 Million across Planning Areas

realis <- fread('data/realis2018.csv')
realis$pa <- toupper(realis$`Planning Area`)
centroids <- readOGR('data/DGP Centroids.shp')
OGR data source with driver: ESRI Shapefile 
Source: "/Users/ellpeeaxe/Desktop/SMU/Y4S2/ASSR/Project/TakeHome/data/DGP Centroids.shp", layer: "DGP Centroids"
with 52 features
It has 3 fields
centroids <-  spTransform(centroids, CRS("+proj=moll +ellps=WGS84"))
dgp <- readOGR("data/MP14_PLNG_AREA_WEB_PL.shp")
OGR data source with driver: ESRI Shapefile 
Source: "/Users/ellpeeaxe/Desktop/SMU/Y4S2/ASSR/Project/TakeHome/data/MP14_PLNG_AREA_WEB_PL.shp", layer: "MP14_PLNG_AREA_WEB_PL"
with 55 features
It has 12 fields
dgp <-  spTransform(dgp, CRS("+proj=longlat +ellps=GRS80"))
one_unit <- subset(realis, realis$`No. of Units` == 1 & realis$`Transacted Price ($)` <= 2500000)
pa_units <- aggregate(realis$`No. of Units`,
                      by = list(realis$pa),
                      FUN = sum)
colnames(pa_units) = c('PA', 'Units')
m <- merge(dgp,pa_units, by.x ='PLN_AREA_N', by.y = 'PA')

pal <-
  colorBin(palette = brewer.pal(10,"YlGnBu"),
           domain = c(0,2000),
           na.color = "#00000000",
           bins=c(0,5,10,50,100,200,400,600,800,1000,1200,1400,1600,1800,2000))
n too large, allowed maximum for palette YlGnBu is 9
Returning the palette you asked for with that many colors
leaflet(dgp) %>% addTiles() %>% addPolygons(fillColor = ~pal(m$Units), weight = 2,
                                            opacity = 1, color = "grey",
                                            dashArray = "1", fillOpacity = 0.8) %>% 
                                addLegend("topright", pal, values=(0:2000), title = "Transacted Units", labFormat = labelFormat(suffix = " Units", between = '-')) %>%       
                                addLabelOnlyMarkers(data = centroids, lng = ~cen_x_dgp, lat = ~cen_y_dgp, label = ~DGP,
                                                    labelOptions = labelOptions(noHide = TRUE, direction = 'center', textOnly = TRUE))

Units Per Planning Area

pa_units[order(-pa_units$Units),]

Available Flats by Planning Area

Total Stock

stock_data <- fread("data/stock2019Q4.csv")
stock_data$PA <- toupper(stock_data$PA)
stock <- merge(dgp,stock_data, by.x ='PLN_AREA_N', by.y = 'PA')
pal <-
  colorBin(palette = brewer.pal(10,"YlGnBu"),
           domain = c(0,2000),
           na.color = "#00000000",
           bins=c(0,500,1000,3000,5000,8000,10000,15000,20000,30000))
n too large, allowed maximum for palette YlGnBu is 9
Returning the palette you asked for with that many colors
leaflet(dgp) %>% addTiles() %>% 
                 addPolygons(fillColor = ~pal(stock$Total), weight = 2,
                             opacity = 1, color = "grey",
                             dashArray = "1", fillOpacity = 0.8) %>% 
                 addLegend("topright", pal, values=(0:2000), 
                           title = "Total Stock", 
                           labFormat = labelFormat(suffix = " Units", between = '-')) %>%
                 addLabelOnlyMarkers(data = centroids, lng = ~cen_x_dgp, lat = ~cen_y_dgp, label = ~DGP,
                                     labelOptions = labelOptions(noHide = TRUE, direction = 'center', textOnly = TRUE))

Stock Per Planning Area

stock_data[order(-stock_data$Total),]

ANOVA and Tukey on PSM per Planning Area for each Flat Type

PropType <- unique(data$Property.Type)
for (k in 1:length(unique(data$Property.Type))){
  data_property <-  data %>%  filter(Property.Type==unique(data$Property.Type)[k])
    res.aov <- aov(Unit.Price....psm.~Planning.Area,data=data_property)
    summary(res.aov)
    results <-  tidy(TukeyHSD(res.aov,ordered=TRUE))
    results_sorted <-  results %>% separate(comparison, c("Bigger", "Smaller"),sep = "-")
    
  
    rankings1 <-  results_sorted %>%  group_by(Bigger) %>%  summarise(Count=n()) %>%  arrange(desc(Count))
    rankings2 <- results_sorted %>% filter(Smaller %!in% rankings1$Bigger) %>%  group_by(Smaller) %>% summarise(Count=n())%>%  arrange(Count)
    names(rankings2)[1] <- "Bigger"
    rankings <- rbind(rankings1,rankings2)

    rankings$Rank <-  seq.int(nrow(rankings)) 
    rankings %<>% dplyr::select(-Count)
    results_sorted <- left_join(results_sorted, rankings) %>%  arrange(Rank)

 
    rankings$TukeyRank <- NA
    rankings$TukeyRank[1] <- 1
    for( i in 2:nrow(rankings)){
      if(results_sorted$adj.p.value[results_sorted$Bigger==rankings$Bigger[i-1]&results_sorted$Smaller == rankings$Bigger[i]]<=0.05){
        rankings$TukeyRank[i] <- rankings$TukeyRank[i-1]+1
      } else if(sum((results_sorted$Smaller[results_sorted$Bigger==rankings$Bigger[i-1]&results_sorted$adj.p.value<=0.05] %in% results_sorted$Smaller[results_sorted$Bigger==rankings$Bigger[i]&results_sorted$adj.p.value>0.05])>0)){
        rankings$TukeyRank[i] <- rankings$TukeyRank[i-1]+1
       } else {
         rankings$TukeyRank[i] <- rankings$TukeyRank[i-1]
       }
     }
    
    Tukey_ranked <-  rankings %>%  dplyr::select(Bigger,TukeyRank)
    names(Tukey_ranked)[1] <-  "Planning.Area"
    Planning_Mean <-  data_property %>% group_by(Planning.Area) %>%  summarise(mean= mean(Unit.Price....psm.), sd= sd(Unit.Price....psm.))
    Tukey_ranked <- left_join(Tukey_ranked, Planning_Mean)
    
    assign(paste("aov_", PropType[k], sep = ""), tidy(res.aov))
    assign(paste("Tukey_", PropType[k], sep = ""), as.data.frame(Tukey_ranked))
}
Joining, by = "Bigger"Joining, by = "Planning.Area"Column `Planning.Area` joining character vector and factor, coercing into character vectorJoining, by = "Bigger"Joining, by = "Planning.Area"Column `Planning.Area` joining character vector and factor, coercing into character vectorJoining, by = "Bigger"Joining, by = "Planning.Area"Column `Planning.Area` joining character vector and factor, coercing into character vectorJoining, by = "Bigger"Joining, by = "Planning.Area"Column `Planning.Area` joining character vector and factor, coercing into character vectorJoining, by = "Bigger"Joining, by = "Planning.Area"Column `Planning.Area` joining character vector and factor, coercing into character vectorJoining, by = "Bigger"Joining, by = "Planning.Area"Column `Planning.Area` joining character vector and factor, coercing into character vector
summary(res.aov)
                Df    Sum Sq   Mean Sq F value Pr(>F)    
Planning.Area   14 2.071e+09 147906034   216.6 <2e-16 ***
Residuals     1731 1.182e+09    682842                   
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Apartments Mean PSM

Tukey_Apartment

Condominium Mean PSM

Tukey_Condominium

Executive Condominium Mean PSM

`Tukey_Executive Condominium`

Detailed analysis

We performed the ANOVA test to determine if all the mean PSM per planning areas were similar. At 95% Confidence level, as P value is less than 0.05, we have sufficient evidence to say to reject H0 and that the mean price per square meter(psm) of flats are significantly different. To compare the group means, a post hoc test - TUKEY test - was performed per property type. Given the extensive results output, the TUKEY results were sorted for clarity. For each property type, the differences were sorted, filtered for results with adjusted p-value <=0.05 and then ranked accordingly.

At 95% confidence intervals:

  • For property type - Apartments - the Orchard area compared with the other planning areas, has the most significantly different differences between means. River Valley, Newton and Downtown core are ranked 2nd.
  • For property type - Condominium - the Orchard and River Vallye areas compared with the other planning areas, have the most significantly different differences between means. The Newton area comes in second
  • For property type - EC - the Bishan area compared with the other planning areas, has the most significantly different differences between means.Sengkang, Punggol areas come in second

ANOVA and Tukey on Transacted Price per Planning Area for each Flat Type

for (k in 1:length(unique(data$Property.Type))){
  data_property <-  data %>%  filter(Property.Type==unique(data$Property.Type)[k])
    res.aov <- aov(Transacted.Price....~Planning.Area,data=data_property)
    summary(res.aov)
    results <-  tidy(TukeyHSD(res.aov,ordered=TRUE))
    results_sorted <-  results %>% separate(comparison, c("Bigger", "Smaller"),sep = "-")
    
  
    rankings1 <-  results_sorted %>%  group_by(Bigger) %>%  summarise(Count=n()) %>%  arrange(desc(Count))
    rankings2 <- results_sorted %>% filter(Smaller %!in% rankings1$Bigger) %>%  group_by(Smaller) %>% summarise(Count=n())%>%  arrange(Count)
    names(rankings2)[1] <- "Bigger"
    rankings <- rbind(rankings1,rankings2)

    rankings$Rank <-  seq.int(nrow(rankings)) 
    rankings %<>% dplyr::select(-Count)
    results_sorted <- left_join(results_sorted, rankings) %>%  arrange(Rank)

 
    rankings$TukeyRank <- NA
    rankings$TukeyRank[1] <- 1
    for( i in 2:nrow(rankings)){
      if(results_sorted$adj.p.value[results_sorted$Bigger==rankings$Bigger[i-1]&results_sorted$Smaller == rankings$Bigger[i]]<=0.05){
        rankings$TukeyRank[i] <- rankings$TukeyRank[i-1]+1
      } else if(sum((results_sorted$Smaller[results_sorted$Bigger==rankings$Bigger[i-1]&results_sorted$adj.p.value<=0.05] %in% results_sorted$Smaller[results_sorted$Bigger==rankings$Bigger[i]&results_sorted$adj.p.value>0.05])>0)){
        rankings$TukeyRank[i] <- rankings$TukeyRank[i-1]+1
       } else {
         rankings$TukeyRank[i] <- rankings$TukeyRank[i-1]
       }
     }
    
    Tukey_ranked <-  rankings %>%  dplyr::select(Bigger,TukeyRank)
    names(Tukey_ranked)[1] <-  "Planning.Area"
    Planning_Mean <-  data_property %>% group_by(Planning.Area) %>%  summarise(mean= mean(Transacted.Price....), sd= sd(Transacted.Price....))
    Tukey_ranked <- left_join(Tukey_ranked, Planning_Mean)
    
    assign(paste("aov_", PropType[k], sep = ""), tidy(res.aov))
    assign(paste("Tukey_", PropType[k], sep = ""), as.data.frame(Tukey_ranked))
}
Joining, by = "Bigger"Joining, by = "Planning.Area"Column `Planning.Area` joining character vector and factor, coercing into character vectorJoining, by = "Bigger"Joining, by = "Planning.Area"Column `Planning.Area` joining character vector and factor, coercing into character vectorJoining, by = "Bigger"Joining, by = "Planning.Area"Column `Planning.Area` joining character vector and factor, coercing into character vectorJoining, by = "Bigger"Joining, by = "Planning.Area"Column `Planning.Area` joining character vector and factor, coercing into character vectorJoining, by = "Bigger"Joining, by = "Planning.Area"Column `Planning.Area` joining character vector and factor, coercing into character vectorJoining, by = "Bigger"Joining, by = "Planning.Area"Column `Planning.Area` joining character vector and factor, coercing into character vector
summary(res.aov)
                Df    Sum Sq   Mean Sq F value Pr(>F)    
Planning.Area   14 1.689e+13 1.206e+12   65.86 <2e-16 ***
Residuals     1731 3.170e+13 1.831e+10                   
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Apartments Mean Transacted Price

Tukey_Apartment

Condominium Mean Transacted Price

Tukey_Condominium

Executive Condominium Mean Transacted Price

`Tukey_Executive Condominium`

Detailed analysis

We performed the ANOVA test to determine if the mean transacted price per planning areas were similar. At 95% Confidence level, as P value is less than 0.05, we have sufficient evidence to say to reject H0 and that the mean transacted price of flats are significantly different. To compare the group means, a post hoc test - TUKEY test - was performed for each property type. Given the extensive results output, the TUKEY results were sorted for clarity. For each property type, the differences were sorted, filtered for results with adjusted p-value <=0.05 and then ranked accordingly.

At 95% confidence intervals:

  • For property type - apartments - the Orchard area compared with the other planning areas, has the most significantly different differences between means. Newton and Downtown core are ranked 2nd.This is
  • For property type - Condominium - the Newton and Tanglin areas compared with the other planning areas, have the most significantly different differences between means. Orchard and River Valley areas come in second
  • For property type - EC - the Bishan area compared with the other planning areas, has the most significantly different differences between means. Ang Mo Kio and Bukit Batok areas are ranked second and third respectively

MRT Stations(Existing and Planned) by Planning Area

mrt <- fread("data/Planning_area_mrt_stations.csv")
mrt$`Planning Area` <- toupper(mrt$`Planning Area`)
centroids <- readOGR('data/DGP Centroids.shp')
OGR data source with driver: ESRI Shapefile 
Source: "/Users/ellpeeaxe/Desktop/SMU/Y4S2/ASSR/Project/TakeHome/data/DGP Centroids.shp", layer: "DGP Centroids"
with 52 features
It has 3 fields
centroids <-  spTransform(centroids, CRS("+proj=moll +ellps=WGS84"))
dgp <- readOGR("data/MP14_PLNG_AREA_WEB_PL.shp")
OGR data source with driver: ESRI Shapefile 
Source: "/Users/ellpeeaxe/Desktop/SMU/Y4S2/ASSR/Project/TakeHome/data/MP14_PLNG_AREA_WEB_PL.shp", layer: "MP14_PLNG_AREA_WEB_PL"
with 55 features
It has 12 fields
dgp <-  spTransform(dgp, CRS("+proj=longlat +ellps=GRS80"))
mrt2 <- merge(dgp,mrt, by.x ='PLN_AREA_N', by.y = 'Planning Area')

#All MRT Stations
mrt_pal <- colorFactor(palette= brewer.pal(15, 'RdYlGn'),
                        domain=c(0,1,2,3,5,6,7,8,9,10,12,14),
                        na.color = "#00000000")
n too large, allowed maximum for palette RdYlGn is 11
Returning the palette you asked for with that many colors
 
leaflet(dgp) %>% addTiles() %>% addPolygons(fillColor = ~mrt_pal(mrt2$`Total no. of stations`), weight = 2,
                                            opacity = 1, color = "grey",
                                            dashArray = "1", fillOpacity = 0.8) %>% 
                                            addLegend("topright", mrt_pal, values=(0:14), title= "MRT Stations", 
                                                      labFormat = labelFormat(suffix = " stations")) %>%
                                            addLabelOnlyMarkers(data = centroids, lng = ~cen_x_dgp, lat = ~cen_y_dgp, label = ~DGP,
                                                                labelOptions = labelOptions(noHide = TRUE, direction = 'center', textOnly = TRUE))
Some values were outside the color scale and will be treated as NASome values were outside the color scale and will be treated as NA

#Only current MRT stations
mrt_pal2 <- colorFactor(palette= brewer.pal(12, 'RdYlGn'),
                        domain=c(0,1,2,3,4,5,6,7,8,9,10,11),
                        na.color = "#00000000")
n too large, allowed maximum for palette RdYlGn is 11
Returning the palette you asked for with that many colors
leaflet(dgp) %>% addTiles() %>% addPolygons(fillColor = ~mrt_pal2(mrt2$`No. of operational stations`), weight = 2,
                                            opacity = 1, color = "grey",
                                            dashArray = "1", fillOpacity = 0.8) %>% 
                                            addLegend("topright", mrt_pal2, values=(0:11), title= "MRT Stations", 
                                                      labFormat = labelFormat(suffix = " stations")) %>%
                                            addLabelOnlyMarkers(data = centroids, lng = ~cen_x_dgp, lat = ~cen_y_dgp, label = ~DGP,
                                                                labelOptions = labelOptions(noHide = TRUE, direction = 'center', textOnly = TRUE))

Number of Operational MRT Stations

mrt[order(-mrt$`No. of operational stations`),]

Total Number of MRT Stations (Operational + Planned)

mrt[order(-mrt$`Total no. of stations`),]

Detailed Analysis

The optimal location to purchase will depend on what we want from the property. Assuming that we are looking to purchase an upscale Flat for investment purposes, the Newton area could be an option but the mean prices are high and mostly beyond our budget. There is also only 1 MRT station and limited supply of properties on sale. A better alternative would be the River Valley planning area as it has one of the highest number of available “Apartments” within our budget of $2.5 million. However, accessibility is lower than that of the other planning areas as there are currently only 1 MRT stations with 2 stations planned. Despite this, it should still be relatively accessible as it is very close to the CBD.

If we are looking to buy a flat to live in, we will be more concerned about the accessibility as well as value for money. In this regard, we would prefer planning areas like Bedok or Toa Payoh as they are mature estate with relatively high levels of accessibility and amenities. Toa Payoh has an integrated transport hub while Bedok has many upcoming future MRT stations bringing the total to 11 stations. In addition the apartments in these areas are more affordable and we could even purchase 2 if we wanted to.

Alternatively, if we are simply looking for a larger space at the cheapest possible rates and save the rest of the money for other uses, the best option would be Jurong East or Jurong West as they have one of the lowest psm. In addition, it is also a mature estate with multiple malls and decent accessibility. There will be a total of 15 MRT stations in the area in the near future.

LS0tCnRpdGxlOiAiVGFrZS1Ib21lIEdyb3VwIEFzc2lnbm1lbnQiCgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7cn0KIyBJbnN0YWxsIHJlcXVpcmVkIGxpYnJhcmllcwpwYWNrYWdlcyA8LSBjKCdsZWFmbGV0JywnZHBseXInLCdkYXRhLnRhYmxlJywnc3AnLCAncmdlb3MnLCAncmFzdGVyJywgCidyZ2RhbCcsJ0dJU1Rvb2xzJywnbWFncml0dHInLCdCU0RBJywgJ1BBU1dSJywnYnJvb20nLCd0aWR5dmVyc2UnLCdndG9vbHMnKQoKZm9yKHAgaW4gcGFja2FnZXMpewogIGlmKCFyZXF1aXJlKHAsY2hhcmFjdGVyLm9ubHkgPSBUKSl7CiAgICBpbnN0YWxsLnBhY2thZ2VzKHApCiAgfQogIGxpYnJhcnkocCxjaGFyYWN0ZXIub25seSA9IFQpCn0KCiclIWluJScgPC0gZnVuY3Rpb24oeCx5KSEoJyVpbiUnKHgseSkpCmBgYAoKYGBge3J9CiMgSW5zdGFsbCByZXF1aXJlZCBsaWJyYXJpZXMKZGF0YSA8LSByZWFkLmNzdigiZGF0YS9yZWFsaXMyMDE4LmNzdiIpCmBgYAoKIyMgUHJvYmxlbSBEZXNjcmlwdGlvbgpPbmUgZGF5LCB5b3VyIG11bSB0ZWxscyB5b3UgdGhhdCB3ZSBqdXN0IHdvbiB0aGUgZmlyc3QgcHJpemUgb2YgVE9UTyB3aGljaCB3b3J0aCAyLDUwMCwwMDAgU0dELiBBZnRlciB5b3UgZGlzY3VzcyB3aXRoIHlvdXIgZmFtaWx5LCB5b3UgZGVjaWRlIHRvIGJ1eSBhIGZsYXQgYW5kIHBsYW4gZm9yIHRoZSBpbnZlc3RtZW50LiBEdXJpbmcgdGhlIHBlcmlvZCBvZiB0aW1lLCB5b3UgY29udGFjdCB0aGUgY29tcGFueSAiUHJvcGVydHkgTWFzdGVyQCIgdG8gZGlzY3VzcyBtb3JlIG9uIHRoZSBoaXN0b3JpY2FsIHRyYW5zYWN0aW9uIG9mIFNpbmdhcG9yZSBwcm9wZXJ0eS4gVGhlIHNhbXBsZSBkYXRhIGdpdmVuaXMg4oCccmVhbGlzMjAxOC5jc3bigJ0uIEZvciBwcm9ibGVtcyBzdGF0ZWQgYmVsb3csIHdlIHVzZSDOsT0gNSUuCgpgYGB7cn0KIyBmaWx0ZXIgbm8uIG9mIHVuaXRzID0gMSBjb3Mgc29tZSB0cmFuc2FjdGlvbnMgYXJlIHRoZSB3aG9sZSBidWlsZGluZwpkYXRhICU8PiUgZmlsdGVyKE5vLi5vZi5Vbml0cz09MSkKCiMgcmVjb2RlIFlJU0hVTiBhbmQgWWlzaHVuCmRhdGEkUGxhbm5pbmcuQXJlYSA8LSByZWNvZGUoZGF0YSRQbGFubmluZy5BcmVhLCJZSVNIVU4iPSJZaXNodW4iKQpgYGAKCiMgUHJvYmxlbSAxCllvdSBsb29rIGFyb3VuZCBmZXcgZGlmZmVyZW50IHBsYW5uaW5nIGFyZWFzIChDb2x1bW4gUikuIFdoZW4geW91IGFzayB0aGUgYWdlbnQgd2hhdCBpcyB0aGUgbWVhbiBVbml0IHByaWNlKHBzbSkgZm9yIE5ld3RvbiBmbGF0cyAoQ29sdW1uIEcpLCBzaGUgY2xhaW1zIHRoYXQgdGhlIG1lYW4gaXMgaGlnaGVyIHRoYW4gMjY1MDAuIERvIHlvdSBhZ3JlZSB3aXRoIHlvdXIgYWdlbnTigJlzIHN1Z2dlc3Rpb24/IEV4cGxhaW4gYW5kIGp1c3RpZnkgeW91ciBhbnN3ZXIuCgojIyBBc3N1bXB0aW9uczoKKiBBc3N1bWUgIkZsYXQiIG1lYW5zIGVpdGhlciBDb25kb21pbml1bSwgRXhlY3V0aXZlIENvbmRvbWluaXVtIG9yIEFwYXJ0bWVudAoqIERhdGEgaXMgYSBzYW1wbGUgb2YgaG91c2luZyBwcmljZXMgaW4gMjAxOAoKIyMgQ29kZQpgYGB7cn0KTmV3dG9uIDwtICBkYXRhICU+JSAgZmlsdGVyKFBsYW5uaW5nLkFyZWE9PSJOZXd0b24iLFByb3BlcnR5LlR5cGUgJWluJSBjKCJDb25kb21pbml1bSIsICJBcGFydG1lbnQiLCAiRXhlY3V0aXZlIENvbmRvbWluaXVtIiksIE5vLi5vZi5Vbml0cyA9PSAiMSIpCnoudGVzdChOZXd0b24kVW5pdC5QcmljZS4uLi5wc20sIGFsdGVybmF0aXZlID0gImdyZWF0ZXIiLCBtdT0gMjY1MDAsc2lnbWEueD1zZChOZXd0b24kVW5pdC5QcmljZS4uLi5wc20pKQpgYGAKCiMjIERldGFpbGVkIEFuYWx5c2lzCkF0IDk1JSBDb25maWRlbmNlIGxldmVsLCB3ZSBoYXZlIHN1ZmZpY2llbnQgZXZpZGVuY2UgdG8gc2F5IHRoYXQgdGhlIG1lYW4gcHJpY2UgcGVyIHNxdWFyZSBtZXRlcihwc20pIG9mIGZsYXRzIGluIHRoZSBOZXd0b24gQXJlYSBpcyBtb3JlIHRoYW4gJDI2NTAwLgoKCiMgUHJvYmxlbSAyCllvdXIgZnJpZW5kIHRvbGQgeW91IHRoYXQgTmV3dG9uIHBsYW5uaW5nIGFyZWEgbWF5IG5vdCBiZSB0aGUgYmVzdCBhcmVhIHRvIGNob29zZS4gSGUgc3VnZ2VzdGVkIHlvdSB0byBjb25zaWRlciBvdGhlciBwbGFubmluZyBhcmVhcy4gVGhpcyBpcyBhIHZlcnkgZGlmZmljdWx0IGRlY2lzaW9uIHNpbmNlIHlvdSBuZWVkIHRvIGNvbmR1Y3QgYSBtb3JlIGNvbXByZWhlbnNpdmUgYW5hbHlzaXMgYW5kIHlvdSBhbHNvIG5lZWQgdG8ganVzdGlmeSB3aGV0aGVyIHlvdSBzdGlsbCBjaG9vc2UgTmV3dG9uIG9yIGFub3RoZXIgcGxhbm5pbmcgYXJlYS4KCiMjIEFzc3VtcHRpb25zOgoqIEFzc3VtZSBubyBwcmVmZXJlbmNlIGZvciBwbGFubmluZyBhcmVhCiogQXNzdW1lIHB1cmNoYXNpbmcgaW4gUTEgMjAyMAoqIFRoZSBidWRnZXQgaXMgMi41IG1pbGxpb24KKiBBc3N1bWUgIkZsYXQiIG1lYW5zIGVpdGhlciBDb25kb21pbml1bSwgRXhlY3V0aXZlIENvbmRvbWluaXVtIG9yIEFwYXJ0bWVudAoqIEtleSBmYWN0b3JzIHRvIGNvbnNpZGVyOiBBY2Nlc2liaWxpdHksIEVzdGF0ZSBNYXR1cml0eQoqIE1ldHJpYyBmb3IgZXZhbHVhdGlvbiBpcyBQU00KCiMjIENvZGUKIyMjIERpc3RyaWJ1dGlvbiBvZiBQcm9wZXJ0aWVzIDw9IDIuNSBNaWxsaW9uIGFjcm9zcyBQbGFubmluZyBBcmVhcwpgYGB7ciBmaWcuaGVpZ2h0ID0gNCwgZmlnLndpZHRoPTh9CnJlYWxpcyA8LSBmcmVhZCgnZGF0YS9yZWFsaXMyMDE4LmNzdicpCnJlYWxpcyRwYSA8LSB0b3VwcGVyKHJlYWxpcyRgUGxhbm5pbmcgQXJlYWApCmNlbnRyb2lkcyA8LSByZWFkT0dSKCdkYXRhL0RHUCBDZW50cm9pZHMuc2hwJykKY2VudHJvaWRzIDwtICBzcFRyYW5zZm9ybShjZW50cm9pZHMsIENSUygiK3Byb2o9bW9sbCArZWxscHM9V0dTODQiKSkKZGdwIDwtIHJlYWRPR1IoImRhdGEvTVAxNF9QTE5HX0FSRUFfV0VCX1BMLnNocCIpCmRncCA8LSAgc3BUcmFuc2Zvcm0oZGdwLCBDUlMoIitwcm9qPWxvbmdsYXQgK2VsbHBzPUdSUzgwIikpCm9uZV91bml0IDwtIHN1YnNldChyZWFsaXMsIHJlYWxpcyRgTm8uIG9mIFVuaXRzYCA9PSAxICYgcmVhbGlzJGBUcmFuc2FjdGVkIFByaWNlICgkKWAgPD0gMjUwMDAwMCkKcGFfdW5pdHMgPC0gYWdncmVnYXRlKHJlYWxpcyRgTm8uIG9mIFVuaXRzYCwKICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gbGlzdChyZWFsaXMkcGEpLAogICAgICAgICAgICAgICAgICAgICAgRlVOID0gc3VtKQpjb2xuYW1lcyhwYV91bml0cykgPSBjKCdQQScsICdVbml0cycpCm0gPC0gbWVyZ2UoZGdwLHBhX3VuaXRzLCBieS54ID0nUExOX0FSRUFfTicsIGJ5LnkgPSAnUEEnKQoKcGFsIDwtCiAgY29sb3JCaW4ocGFsZXR0ZSA9IGJyZXdlci5wYWwoMTAsIllsR25CdSIpLAogICAgICAgICAgIGRvbWFpbiA9IGMoMCwyMDAwKSwKICAgICAgICAgICBuYS5jb2xvciA9ICIjMDAwMDAwMDAiLAogICAgICAgICAgIGJpbnM9YygwLDUsMTAsNTAsMTAwLDIwMCw0MDAsNjAwLDgwMCwxMDAwLDEyMDAsMTQwMCwxNjAwLDE4MDAsMjAwMCkpCmxlYWZsZXQoZGdwKSAlPiUgYWRkVGlsZXMoKSAlPiUgYWRkUG9seWdvbnMoZmlsbENvbG9yID0gfnBhbChtJFVuaXRzKSwgd2VpZ2h0ID0gMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcGFjaXR5ID0gMSwgY29sb3IgPSAiZ3JleSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGFzaEFycmF5ID0gIjEiLCBmaWxsT3BhY2l0eSA9IDAuOCkgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFkZExlZ2VuZCgidG9wcmlnaHQiLCBwYWwsIHZhbHVlcz0oMDoyMDAwKSwgdGl0bGUgPSAiVHJhbnNhY3RlZCBVbml0cyIsIGxhYkZvcm1hdCA9IGxhYmVsRm9ybWF0KHN1ZmZpeCA9ICIgVW5pdHMiLCBiZXR3ZWVuID0gJy0nKSkgJT4lICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFkZExhYmVsT25seU1hcmtlcnMoZGF0YSA9IGNlbnRyb2lkcywgbG5nID0gfmNlbl94X2RncCwgbGF0ID0gfmNlbl95X2RncCwgbGFiZWwgPSB+REdQLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxPcHRpb25zID0gbGFiZWxPcHRpb25zKG5vSGlkZSA9IFRSVUUsIGRpcmVjdGlvbiA9ICdjZW50ZXInLCB0ZXh0T25seSA9IFRSVUUpKQpgYGAKCiMjIyMgVW5pdHMgUGVyIFBsYW5uaW5nIEFyZWEKYGBge3J9CnBhX3VuaXRzW29yZGVyKC1wYV91bml0cyRVbml0cyksXQpgYGAKCiMjIyBBdmFpbGFibGUgRmxhdHMgYnkgUGxhbm5pbmcgQXJlYQojIyMjIFRvdGFsIFN0b2NrCmBgYHtyIGZpZy5oZWlnaHQgPSA0LCBmaWcud2lkdGg9OH0Kc3RvY2tfZGF0YSA8LSBmcmVhZCgiZGF0YS9zdG9jazIwMTlRNC5jc3YiKQpzdG9ja19kYXRhJFBBIDwtIHRvdXBwZXIoc3RvY2tfZGF0YSRQQSkKc3RvY2sgPC0gbWVyZ2UoZGdwLHN0b2NrX2RhdGEsIGJ5LnggPSdQTE5fQVJFQV9OJywgYnkueSA9ICdQQScpCnBhbCA8LQogIGNvbG9yQmluKHBhbGV0dGUgPSBicmV3ZXIucGFsKDEwLCJZbEduQnUiKSwKICAgICAgICAgICBkb21haW4gPSBjKDAsMjAwMCksCiAgICAgICAgICAgbmEuY29sb3IgPSAiIzAwMDAwMDAwIiwKICAgICAgICAgICBiaW5zPWMoMCw1MDAsMTAwMCwzMDAwLDUwMDAsODAwMCwxMDAwMCwxNTAwMCwyMDAwMCwzMDAwMCkpCmxlYWZsZXQoZGdwKSAlPiUgYWRkVGlsZXMoKSAlPiUgCiAgICAgICAgICAgICAgICAgYWRkUG9seWdvbnMoZmlsbENvbG9yID0gfnBhbChzdG9jayRUb3RhbCksIHdlaWdodCA9IDIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3BhY2l0eSA9IDEsIGNvbG9yID0gImdyZXkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhc2hBcnJheSA9ICIxIiwgZmlsbE9wYWNpdHkgPSAwLjgpICU+JSAKICAgICAgICAgICAgICAgICBhZGRMZWdlbmQoInRvcHJpZ2h0IiwgcGFsLCB2YWx1ZXM9KDA6MjAwMCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICJUb3RhbCBTdG9jayIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJGb3JtYXQgPSBsYWJlbEZvcm1hdChzdWZmaXggPSAiIFVuaXRzIiwgYmV0d2VlbiA9ICctJykpICU+JQogICAgICAgICAgICAgICAgIGFkZExhYmVsT25seU1hcmtlcnMoZGF0YSA9IGNlbnRyb2lkcywgbG5nID0gfmNlbl94X2RncCwgbGF0ID0gfmNlbl95X2RncCwgbGFiZWwgPSB+REdQLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxPcHRpb25zID0gbGFiZWxPcHRpb25zKG5vSGlkZSA9IFRSVUUsIGRpcmVjdGlvbiA9ICdjZW50ZXInLCB0ZXh0T25seSA9IFRSVUUpKQpgYGAKCiMjIyMgU3RvY2sgUGVyIFBsYW5uaW5nIEFyZWEKYGBge3J9CnN0b2NrX2RhdGFbb3JkZXIoLXN0b2NrX2RhdGEkVG90YWwpLF0KYGBgCgojIyMgQU5PVkEgYW5kIFR1a2V5IG9uIFBTTSBwZXIgUGxhbm5pbmcgQXJlYSBmb3IgZWFjaCBGbGF0IFR5cGUKYGBge3J9ClByb3BUeXBlIDwtIHVuaXF1ZShkYXRhJFByb3BlcnR5LlR5cGUpCmZvciAoayBpbiAxOmxlbmd0aCh1bmlxdWUoZGF0YSRQcm9wZXJ0eS5UeXBlKSkpewogIGRhdGFfcHJvcGVydHkgPC0gIGRhdGEgJT4lICBmaWx0ZXIoUHJvcGVydHkuVHlwZT09dW5pcXVlKGRhdGEkUHJvcGVydHkuVHlwZSlba10pCiAgICByZXMuYW92IDwtIGFvdihVbml0LlByaWNlLi4uLnBzbS5+UGxhbm5pbmcuQXJlYSxkYXRhPWRhdGFfcHJvcGVydHkpCiAgICBzdW1tYXJ5KHJlcy5hb3YpCiAgICByZXN1bHRzIDwtICB0aWR5KFR1a2V5SFNEKHJlcy5hb3Ysb3JkZXJlZD1UUlVFKSkKICAgIHJlc3VsdHNfc29ydGVkIDwtICByZXN1bHRzICU+JSBzZXBhcmF0ZShjb21wYXJpc29uLCBjKCJCaWdnZXIiLCAiU21hbGxlciIpLHNlcCA9ICItIikKICAgIAogIAogICAgcmFua2luZ3MxIDwtICByZXN1bHRzX3NvcnRlZCAlPiUgIGdyb3VwX2J5KEJpZ2dlcikgJT4lICBzdW1tYXJpc2UoQ291bnQ9bigpKSAlPiUgIGFycmFuZ2UoZGVzYyhDb3VudCkpCiAgICByYW5raW5nczIgPC0gcmVzdWx0c19zb3J0ZWQgJT4lIGZpbHRlcihTbWFsbGVyICUhaW4lIHJhbmtpbmdzMSRCaWdnZXIpICU+JSAgZ3JvdXBfYnkoU21hbGxlcikgJT4lIHN1bW1hcmlzZShDb3VudD1uKCkpJT4lICBhcnJhbmdlKENvdW50KQogICAgbmFtZXMocmFua2luZ3MyKVsxXSA8LSAiQmlnZ2VyIgogICAgcmFua2luZ3MgPC0gcmJpbmQocmFua2luZ3MxLHJhbmtpbmdzMikKCiAgICByYW5raW5ncyRSYW5rIDwtICBzZXEuaW50KG5yb3cocmFua2luZ3MpKSAKICAgIHJhbmtpbmdzICU8PiUgZHBseXI6OnNlbGVjdCgtQ291bnQpCiAgICByZXN1bHRzX3NvcnRlZCA8LSBsZWZ0X2pvaW4ocmVzdWx0c19zb3J0ZWQsIHJhbmtpbmdzKSAlPiUgIGFycmFuZ2UoUmFuaykKCiAKICAgIHJhbmtpbmdzJFR1a2V5UmFuayA8LSBOQQogICAgcmFua2luZ3MkVHVrZXlSYW5rWzFdIDwtIDEKICAgIGZvciggaSBpbiAyOm5yb3cocmFua2luZ3MpKXsKICAgICAgaWYocmVzdWx0c19zb3J0ZWQkYWRqLnAudmFsdWVbcmVzdWx0c19zb3J0ZWQkQmlnZ2VyPT1yYW5raW5ncyRCaWdnZXJbaS0xXSZyZXN1bHRzX3NvcnRlZCRTbWFsbGVyID09IHJhbmtpbmdzJEJpZ2dlcltpXV08PTAuMDUpewogICAgICAgIHJhbmtpbmdzJFR1a2V5UmFua1tpXSA8LSByYW5raW5ncyRUdWtleVJhbmtbaS0xXSsxCiAgICAgIH0gZWxzZSBpZihzdW0oKHJlc3VsdHNfc29ydGVkJFNtYWxsZXJbcmVzdWx0c19zb3J0ZWQkQmlnZ2VyPT1yYW5raW5ncyRCaWdnZXJbaS0xXSZyZXN1bHRzX3NvcnRlZCRhZGoucC52YWx1ZTw9MC4wNV0gJWluJSByZXN1bHRzX3NvcnRlZCRTbWFsbGVyW3Jlc3VsdHNfc29ydGVkJEJpZ2dlcj09cmFua2luZ3MkQmlnZ2VyW2ldJnJlc3VsdHNfc29ydGVkJGFkai5wLnZhbHVlPjAuMDVdKT4wKSl7CiAgICAgICAgcmFua2luZ3MkVHVrZXlSYW5rW2ldIDwtIHJhbmtpbmdzJFR1a2V5UmFua1tpLTFdKzEKICAgICAgIH0gZWxzZSB7CiAgICAgICAgIHJhbmtpbmdzJFR1a2V5UmFua1tpXSA8LSByYW5raW5ncyRUdWtleVJhbmtbaS0xXQogICAgICAgfQogICAgIH0KICAgIAogICAgVHVrZXlfcmFua2VkIDwtICByYW5raW5ncyAlPiUgIGRwbHlyOjpzZWxlY3QoQmlnZ2VyLFR1a2V5UmFuaykKICAgIG5hbWVzKFR1a2V5X3JhbmtlZClbMV0gPC0gICJQbGFubmluZy5BcmVhIgogICAgUGxhbm5pbmdfTWVhbiA8LSAgZGF0YV9wcm9wZXJ0eSAlPiUgZ3JvdXBfYnkoUGxhbm5pbmcuQXJlYSkgJT4lICBzdW1tYXJpc2UobWVhbj0gbWVhbihVbml0LlByaWNlLi4uLnBzbS4pLCBzZD0gc2QoVW5pdC5QcmljZS4uLi5wc20uKSkKICAgIFR1a2V5X3JhbmtlZCA8LSBsZWZ0X2pvaW4oVHVrZXlfcmFua2VkLCBQbGFubmluZ19NZWFuKQogICAgCiAgICBhc3NpZ24ocGFzdGUoImFvdl8iLCBQcm9wVHlwZVtrXSwgc2VwID0gIiIpLCB0aWR5KHJlcy5hb3YpKQogICAgYXNzaWduKHBhc3RlKCJUdWtleV8iLCBQcm9wVHlwZVtrXSwgc2VwID0gIiIpLCBhcy5kYXRhLmZyYW1lKFR1a2V5X3JhbmtlZCkpCn0KYGBgCmBgYHtyfQpzdW1tYXJ5KHJlcy5hb3YpCmBgYAojIyMjIEFwYXJ0bWVudHMgTWVhbiBQU00KYGBge3J9ClR1a2V5X0FwYXJ0bWVudApgYGAKIyMjIyBDb25kb21pbml1bSBNZWFuIFBTTQpgYGB7cn0KVHVrZXlfQ29uZG9taW5pdW0KYGBgCiMjIyMgRXhlY3V0aXZlIENvbmRvbWluaXVtIE1lYW4gUFNNCmBgYHtyfQpgVHVrZXlfRXhlY3V0aXZlIENvbmRvbWluaXVtYApgYGAKCgojIyMjIERldGFpbGVkIGFuYWx5c2lzIAoKV2UgcGVyZm9ybWVkIHRoZSBBTk9WQSB0ZXN0IHRvIGRldGVybWluZSBpZiBhbGwgdGhlIG1lYW4gUFNNIHBlciBwbGFubmluZyBhcmVhcyB3ZXJlIHNpbWlsYXIuIEF0IDk1JSBDb25maWRlbmNlIGxldmVsLCBhcyBQIHZhbHVlIGlzIGxlc3MgdGhhbiAwLjA1LCB3ZSBoYXZlIHN1ZmZpY2llbnQgZXZpZGVuY2UgdG8gc2F5IHRvIHJlamVjdCBIMCBhbmQgdGhhdCB0aGUgbWVhbiBwcmljZSBwZXIgc3F1YXJlIG1ldGVyKHBzbSkgb2YgZmxhdHMgYXJlIHNpZ25pZmljYW50bHkgZGlmZmVyZW50LiBUbyBjb21wYXJlIHRoZSBncm91cCBtZWFucywgYSBwb3N0IGhvYyB0ZXN0IC0gIFRVS0VZIHRlc3QgLSB3YXMgcGVyZm9ybWVkIHBlciBwcm9wZXJ0eSB0eXBlLiBHaXZlbiB0aGUgZXh0ZW5zaXZlIHJlc3VsdHMgb3V0cHV0LCB0aGUgVFVLRVkgcmVzdWx0cyB3ZXJlIHNvcnRlZCBmb3IgY2xhcml0eS4gRm9yIGVhY2ggcHJvcGVydHkgdHlwZSwgdGhlIGRpZmZlcmVuY2VzIHdlcmUgc29ydGVkLCBmaWx0ZXJlZCBmb3IgcmVzdWx0cyB3aXRoIGFkanVzdGVkIHAtdmFsdWUgPD0wLjA1IGFuZCB0aGVuIHJhbmtlZCBhY2NvcmRpbmdseS4KCioqQXQgOTUlIGNvbmZpZGVuY2UgaW50ZXJ2YWxzOioqCgoKKiBGb3IgcHJvcGVydHkgdHlwZSAtIEFwYXJ0bWVudHMgLSB0aGUgT3JjaGFyZCBhcmVhIGNvbXBhcmVkIHdpdGggdGhlIG90aGVyIHBsYW5uaW5nIGFyZWFzLCBoYXMgdGhlIG1vc3Qgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQgZGlmZmVyZW5jZXMgYmV0d2VlbiBtZWFucy4gUml2ZXIgVmFsbGV5LCBOZXd0b24gYW5kIERvd250b3duIGNvcmUgYXJlIHJhbmtlZCAybmQuCiogRm9yIHByb3BlcnR5IHR5cGUgLSBDb25kb21pbml1bSAtIHRoZSBPcmNoYXJkIGFuZCBSaXZlciBWYWxseWUgYXJlYXMgY29tcGFyZWQgd2l0aCB0aGUgb3RoZXIgcGxhbm5pbmcgYXJlYXMsIGhhdmUgdGhlIG1vc3Qgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQgZGlmZmVyZW5jZXMgYmV0d2VlbiBtZWFucy4gVGhlIE5ld3RvbiBhcmVhIGNvbWVzIGluIHNlY29uZAoqIEZvciBwcm9wZXJ0eSB0eXBlIC0gRUMgLSB0aGUgQmlzaGFuIGFyZWEgY29tcGFyZWQgd2l0aCB0aGUgb3RoZXIgcGxhbm5pbmcgYXJlYXMsIGhhcyB0aGUgbW9zdCBzaWduaWZpY2FudGx5IGRpZmZlcmVudCBkaWZmZXJlbmNlcyBiZXR3ZWVuIG1lYW5zLlNlbmdrYW5nLCBQdW5nZ29sIGFyZWFzIGNvbWUgaW4gc2Vjb25kIAoKCiMjIyBBTk9WQSBhbmQgVHVrZXkgb24gVHJhbnNhY3RlZCBQcmljZSBwZXIgUGxhbm5pbmcgQXJlYSBmb3IgZWFjaCBGbGF0IFR5cGUKYGBge3J9CmZvciAoayBpbiAxOmxlbmd0aCh1bmlxdWUoZGF0YSRQcm9wZXJ0eS5UeXBlKSkpewogIGRhdGFfcHJvcGVydHkgPC0gIGRhdGEgJT4lICBmaWx0ZXIoUHJvcGVydHkuVHlwZT09dW5pcXVlKGRhdGEkUHJvcGVydHkuVHlwZSlba10pCiAgICByZXMuYW92IDwtIGFvdihUcmFuc2FjdGVkLlByaWNlLi4uLn5QbGFubmluZy5BcmVhLGRhdGE9ZGF0YV9wcm9wZXJ0eSkKICAgIHN1bW1hcnkocmVzLmFvdikKICAgIHJlc3VsdHMgPC0gIHRpZHkoVHVrZXlIU0QocmVzLmFvdixvcmRlcmVkPVRSVUUpKQogICAgcmVzdWx0c19zb3J0ZWQgPC0gIHJlc3VsdHMgJT4lIHNlcGFyYXRlKGNvbXBhcmlzb24sIGMoIkJpZ2dlciIsICJTbWFsbGVyIiksc2VwID0gIi0iKQogICAgCiAgCiAgICByYW5raW5nczEgPC0gIHJlc3VsdHNfc29ydGVkICU+JSAgZ3JvdXBfYnkoQmlnZ2VyKSAlPiUgIHN1bW1hcmlzZShDb3VudD1uKCkpICU+JSAgYXJyYW5nZShkZXNjKENvdW50KSkKICAgIHJhbmtpbmdzMiA8LSByZXN1bHRzX3NvcnRlZCAlPiUgZmlsdGVyKFNtYWxsZXIgJSFpbiUgcmFua2luZ3MxJEJpZ2dlcikgJT4lICBncm91cF9ieShTbWFsbGVyKSAlPiUgc3VtbWFyaXNlKENvdW50PW4oKSklPiUgIGFycmFuZ2UoQ291bnQpCiAgICBuYW1lcyhyYW5raW5nczIpWzFdIDwtICJCaWdnZXIiCiAgICByYW5raW5ncyA8LSByYmluZChyYW5raW5nczEscmFua2luZ3MyKQoKICAgIHJhbmtpbmdzJFJhbmsgPC0gIHNlcS5pbnQobnJvdyhyYW5raW5ncykpIAogICAgcmFua2luZ3MgJTw+JSBkcGx5cjo6c2VsZWN0KC1Db3VudCkKICAgIHJlc3VsdHNfc29ydGVkIDwtIGxlZnRfam9pbihyZXN1bHRzX3NvcnRlZCwgcmFua2luZ3MpICU+JSAgYXJyYW5nZShSYW5rKQoKIAogICAgcmFua2luZ3MkVHVrZXlSYW5rIDwtIE5BCiAgICByYW5raW5ncyRUdWtleVJhbmtbMV0gPC0gMQogICAgZm9yKCBpIGluIDI6bnJvdyhyYW5raW5ncykpewogICAgICBpZihyZXN1bHRzX3NvcnRlZCRhZGoucC52YWx1ZVtyZXN1bHRzX3NvcnRlZCRCaWdnZXI9PXJhbmtpbmdzJEJpZ2dlcltpLTFdJnJlc3VsdHNfc29ydGVkJFNtYWxsZXIgPT0gcmFua2luZ3MkQmlnZ2VyW2ldXTw9MC4wNSl7CiAgICAgICAgcmFua2luZ3MkVHVrZXlSYW5rW2ldIDwtIHJhbmtpbmdzJFR1a2V5UmFua1tpLTFdKzEKICAgICAgfSBlbHNlIGlmKHN1bSgocmVzdWx0c19zb3J0ZWQkU21hbGxlcltyZXN1bHRzX3NvcnRlZCRCaWdnZXI9PXJhbmtpbmdzJEJpZ2dlcltpLTFdJnJlc3VsdHNfc29ydGVkJGFkai5wLnZhbHVlPD0wLjA1XSAlaW4lIHJlc3VsdHNfc29ydGVkJFNtYWxsZXJbcmVzdWx0c19zb3J0ZWQkQmlnZ2VyPT1yYW5raW5ncyRCaWdnZXJbaV0mcmVzdWx0c19zb3J0ZWQkYWRqLnAudmFsdWU+MC4wNV0pPjApKXsKICAgICAgICByYW5raW5ncyRUdWtleVJhbmtbaV0gPC0gcmFua2luZ3MkVHVrZXlSYW5rW2ktMV0rMQogICAgICAgfSBlbHNlIHsKICAgICAgICAgcmFua2luZ3MkVHVrZXlSYW5rW2ldIDwtIHJhbmtpbmdzJFR1a2V5UmFua1tpLTFdCiAgICAgICB9CiAgICAgfQogICAgCiAgICBUdWtleV9yYW5rZWQgPC0gIHJhbmtpbmdzICU+JSAgZHBseXI6OnNlbGVjdChCaWdnZXIsVHVrZXlSYW5rKQogICAgbmFtZXMoVHVrZXlfcmFua2VkKVsxXSA8LSAgIlBsYW5uaW5nLkFyZWEiCiAgICBQbGFubmluZ19NZWFuIDwtICBkYXRhX3Byb3BlcnR5ICU+JSBncm91cF9ieShQbGFubmluZy5BcmVhKSAlPiUgIHN1bW1hcmlzZShtZWFuPSBtZWFuKFRyYW5zYWN0ZWQuUHJpY2UuLi4uKSwgc2Q9IHNkKFRyYW5zYWN0ZWQuUHJpY2UuLi4uKSkKICAgIFR1a2V5X3JhbmtlZCA8LSBsZWZ0X2pvaW4oVHVrZXlfcmFua2VkLCBQbGFubmluZ19NZWFuKQogICAgCiAgICBhc3NpZ24ocGFzdGUoImFvdl8iLCBQcm9wVHlwZVtrXSwgc2VwID0gIiIpLCB0aWR5KHJlcy5hb3YpKQogICAgYXNzaWduKHBhc3RlKCJUdWtleV8iLCBQcm9wVHlwZVtrXSwgc2VwID0gIiIpLCBhcy5kYXRhLmZyYW1lKFR1a2V5X3JhbmtlZCkpCn0KYGBgCmBgYHtyfQpzdW1tYXJ5KHJlcy5hb3YpCmBgYAojIyMjIEFwYXJ0bWVudHMgTWVhbiBUcmFuc2FjdGVkIFByaWNlCmBgYHtyfQpUdWtleV9BcGFydG1lbnQKYGBgCiMjIyMgQ29uZG9taW5pdW0gTWVhbiBUcmFuc2FjdGVkIFByaWNlCmBgYHtyfQpUdWtleV9Db25kb21pbml1bQpgYGAKIyMjIyBFeGVjdXRpdmUgQ29uZG9taW5pdW0gTWVhbiBUcmFuc2FjdGVkIFByaWNlCmBgYHtyfQpgVHVrZXlfRXhlY3V0aXZlIENvbmRvbWluaXVtYApgYGAKCiMjIyMgRGV0YWlsZWQgYW5hbHlzaXMgCgpXZSBwZXJmb3JtZWQgdGhlIEFOT1ZBIHRlc3QgdG8gZGV0ZXJtaW5lIGlmIHRoZSBtZWFuIHRyYW5zYWN0ZWQgcHJpY2UgcGVyIHBsYW5uaW5nIGFyZWFzIHdlcmUgc2ltaWxhci4gQXQgOTUlIENvbmZpZGVuY2UgbGV2ZWwsIGFzIFAgdmFsdWUgaXMgbGVzcyB0aGFuIDAuMDUsIHdlIGhhdmUgc3VmZmljaWVudCBldmlkZW5jZSB0byBzYXkgdG8gcmVqZWN0IEgwIGFuZCB0aGF0IHRoZSBtZWFuIHRyYW5zYWN0ZWQgcHJpY2Ugb2YgZmxhdHMgYXJlIHNpZ25pZmljYW50bHkgZGlmZmVyZW50LiBUbyBjb21wYXJlIHRoZSBncm91cCBtZWFucywgYSBwb3N0IGhvYyB0ZXN0IC0gVFVLRVkgdGVzdCAtIHdhcyBwZXJmb3JtZWQgZm9yIGVhY2ggcHJvcGVydHkgdHlwZS4gR2l2ZW4gdGhlIGV4dGVuc2l2ZSByZXN1bHRzIG91dHB1dCwgdGhlIFRVS0VZIHJlc3VsdHMgd2VyZSBzb3J0ZWQgZm9yIGNsYXJpdHkuIEZvciBlYWNoIHByb3BlcnR5IHR5cGUsIHRoZSBkaWZmZXJlbmNlcyB3ZXJlIHNvcnRlZCwgZmlsdGVyZWQgZm9yIHJlc3VsdHMgd2l0aCBhZGp1c3RlZCBwLXZhbHVlIDw9MC4wNSBhbmQgdGhlbiByYW5rZWQgYWNjb3JkaW5nbHkuCgoqKkF0IDk1JSBjb25maWRlbmNlIGludGVydmFsczoqKgoKCiogRm9yIHByb3BlcnR5IHR5cGUgLSBhcGFydG1lbnRzIC0gdGhlIE9yY2hhcmQgYXJlYSBjb21wYXJlZCB3aXRoIHRoZSBvdGhlciBwbGFubmluZyBhcmVhcywgaGFzIHRoZSBtb3N0IHNpZ25pZmljYW50bHkgZGlmZmVyZW50IGRpZmZlcmVuY2VzIGJldHdlZW4gbWVhbnMuIE5ld3RvbiBhbmQgRG93bnRvd24gY29yZSBhcmUgcmFua2VkIDJuZC5UaGlzIGlzIAoqIEZvciBwcm9wZXJ0eSB0eXBlIC0gQ29uZG9taW5pdW0gLSB0aGUgTmV3dG9uIGFuZCBUYW5nbGluIGFyZWFzIGNvbXBhcmVkIHdpdGggdGhlIG90aGVyIHBsYW5uaW5nIGFyZWFzLCBoYXZlIHRoZSBtb3N0IHNpZ25pZmljYW50bHkgZGlmZmVyZW50IGRpZmZlcmVuY2VzIGJldHdlZW4gbWVhbnMuIE9yY2hhcmQgYW5kIFJpdmVyIFZhbGxleSBhcmVhcyBjb21lIGluIHNlY29uZAoqIEZvciBwcm9wZXJ0eSB0eXBlIC0gRUMgLSB0aGUgQmlzaGFuIGFyZWEgY29tcGFyZWQgd2l0aCB0aGUgb3RoZXIgcGxhbm5pbmcgYXJlYXMsIGhhcyB0aGUgbW9zdCBzaWduaWZpY2FudGx5IGRpZmZlcmVudCBkaWZmZXJlbmNlcyBiZXR3ZWVuIG1lYW5zLiBBbmcgTW8gS2lvIGFuZCBCdWtpdCBCYXRvayBhcmVhcyBhcmUgcmFua2VkIHNlY29uZCBhbmQgdGhpcmQgcmVzcGVjdGl2ZWx5IAoKCiMjIyBNUlQgU3RhdGlvbnMoRXhpc3RpbmcgYW5kIFBsYW5uZWQpIGJ5IFBsYW5uaW5nIEFyZWEKYGBge3IgZmlnLmhlaWdodCA9IDQsIGZpZy53aWR0aD04fQptcnQgPC0gZnJlYWQoImRhdGEvUGxhbm5pbmdfYXJlYV9tcnRfc3RhdGlvbnMuY3N2IikKbXJ0JGBQbGFubmluZyBBcmVhYCA8LSB0b3VwcGVyKG1ydCRgUGxhbm5pbmcgQXJlYWApCmNlbnRyb2lkcyA8LSByZWFkT0dSKCdkYXRhL0RHUCBDZW50cm9pZHMuc2hwJykKY2VudHJvaWRzIDwtICBzcFRyYW5zZm9ybShjZW50cm9pZHMsIENSUygiK3Byb2o9bW9sbCArZWxscHM9V0dTODQiKSkKZGdwIDwtIHJlYWRPR1IoImRhdGEvTVAxNF9QTE5HX0FSRUFfV0VCX1BMLnNocCIpCmRncCA8LSAgc3BUcmFuc2Zvcm0oZGdwLCBDUlMoIitwcm9qPWxvbmdsYXQgK2VsbHBzPUdSUzgwIikpCm1ydDIgPC0gbWVyZ2UoZGdwLG1ydCwgYnkueCA9J1BMTl9BUkVBX04nLCBieS55ID0gJ1BsYW5uaW5nIEFyZWEnKQoKI0FsbCBNUlQgU3RhdGlvbnMKbXJ0X3BhbCA8LSBjb2xvckZhY3RvcihwYWxldHRlPSBicmV3ZXIucGFsKDE1LCAnUmRZbEduJyksCiAgICAgICAgICAgICAgICAgICAgICAgIGRvbWFpbj1jKDAsMSwyLDMsNSw2LDcsOCw5LDEwLDEyLDE0KSwKICAgICAgICAgICAgICAgICAgICAgICAgbmEuY29sb3IgPSAiIzAwMDAwMDAwIikKCiAKbGVhZmxldChkZ3ApICU+JSBhZGRUaWxlcygpICU+JSBhZGRQb2x5Z29ucyhmaWxsQ29sb3IgPSB+bXJ0X3BhbChtcnQyJGBUb3RhbCBuby4gb2Ygc3RhdGlvbnNgKSwgd2VpZ2h0ID0gMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcGFjaXR5ID0gMSwgY29sb3IgPSAiZ3JleSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGFzaEFycmF5ID0gIjEiLCBmaWxsT3BhY2l0eSA9IDAuOCkgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFkZExlZ2VuZCgidG9wcmlnaHQiLCBtcnRfcGFsLCB2YWx1ZXM9KDA6MTQpLCB0aXRsZT0gIk1SVCBTdGF0aW9ucyIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJGb3JtYXQgPSBsYWJlbEZvcm1hdChzdWZmaXggPSAiIHN0YXRpb25zIikpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFkZExhYmVsT25seU1hcmtlcnMoZGF0YSA9IGNlbnRyb2lkcywgbG5nID0gfmNlbl94X2RncCwgbGF0ID0gfmNlbl95X2RncCwgbGFiZWwgPSB+REdQLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxPcHRpb25zID0gbGFiZWxPcHRpb25zKG5vSGlkZSA9IFRSVUUsIGRpcmVjdGlvbiA9ICdjZW50ZXInLCB0ZXh0T25seSA9IFRSVUUpKQoKI09ubHkgY3VycmVudCBNUlQgc3RhdGlvbnMKbXJ0X3BhbDIgPC0gY29sb3JGYWN0b3IocGFsZXR0ZT0gYnJld2VyLnBhbCgxMiwgJ1JkWWxHbicpLAogICAgICAgICAgICAgICAgICAgICAgICBkb21haW49YygwLDEsMiwzLDQsNSw2LDcsOCw5LDEwLDExKSwKICAgICAgICAgICAgICAgICAgICAgICAgbmEuY29sb3IgPSAiIzAwMDAwMDAwIikKbGVhZmxldChkZ3ApICU+JSBhZGRUaWxlcygpICU+JSBhZGRQb2x5Z29ucyhmaWxsQ29sb3IgPSB+bXJ0X3BhbDIobXJ0MiRgTm8uIG9mIG9wZXJhdGlvbmFsIHN0YXRpb25zYCksIHdlaWdodCA9IDIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3BhY2l0eSA9IDEsIGNvbG9yID0gImdyZXkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhc2hBcnJheSA9ICIxIiwgZmlsbE9wYWNpdHkgPSAwLjgpICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZGRMZWdlbmQoInRvcHJpZ2h0IiwgbXJ0X3BhbDIsIHZhbHVlcz0oMDoxMSksIHRpdGxlPSAiTVJUIFN0YXRpb25zIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYkZvcm1hdCA9IGxhYmVsRm9ybWF0KHN1ZmZpeCA9ICIgc3RhdGlvbnMiKSkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWRkTGFiZWxPbmx5TWFya2VycyhkYXRhID0gY2VudHJvaWRzLCBsbmcgPSB+Y2VuX3hfZGdwLCBsYXQgPSB+Y2VuX3lfZGdwLCBsYWJlbCA9IH5ER1AsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbE9wdGlvbnMgPSBsYWJlbE9wdGlvbnMobm9IaWRlID0gVFJVRSwgZGlyZWN0aW9uID0gJ2NlbnRlcicsIHRleHRPbmx5ID0gVFJVRSkpCmBgYAojIyMjIE51bWJlciBvZiBPcGVyYXRpb25hbCBNUlQgU3RhdGlvbnMgCmBgYHtyfQptcnRbb3JkZXIoLW1ydCRgTm8uIG9mIG9wZXJhdGlvbmFsIHN0YXRpb25zYCksXQpgYGAKIyMjIyBUb3RhbCBOdW1iZXIgb2YgTVJUIFN0YXRpb25zIChPcGVyYXRpb25hbCArIFBsYW5uZWQpCmBgYHtyfQptcnRbb3JkZXIoLW1ydCRgVG90YWwgbm8uIG9mIHN0YXRpb25zYCksXQpgYGAKCiMjIERldGFpbGVkIEFuYWx5c2lzCgpUaGUgb3B0aW1hbCBsb2NhdGlvbiB0byBwdXJjaGFzZSB3aWxsIGRlcGVuZCBvbiB3aGF0IHdlIHdhbnQgZnJvbSB0aGUgcHJvcGVydHkuIEFzc3VtaW5nIHRoYXQgd2UgYXJlIGxvb2tpbmcgdG8gcHVyY2hhc2UgYW4gdXBzY2FsZSBGbGF0IGZvciBpbnZlc3RtZW50IHB1cnBvc2VzLCB0aGUgTmV3dG9uIGFyZWEgY291bGQgYmUgYW4gb3B0aW9uIGJ1dCB0aGUgbWVhbiBwcmljZXMgYXJlIGhpZ2ggYW5kIG1vc3RseSBiZXlvbmQgb3VyIGJ1ZGdldC4gVGhlcmUgaXMgYWxzbyBvbmx5IDEgTVJUIHN0YXRpb24gYW5kIGxpbWl0ZWQgc3VwcGx5IG9mIHByb3BlcnRpZXMgb24gc2FsZS4gQSBiZXR0ZXIgYWx0ZXJuYXRpdmUgd291bGQgYmUgdGhlIFJpdmVyIFZhbGxleSBwbGFubmluZyBhcmVhIGFzIGl0IGhhcyBvbmUgb2YgdGhlIGhpZ2hlc3QgbnVtYmVyIG9mIGF2YWlsYWJsZSAiQXBhcnRtZW50cyIgd2l0aGluIG91ciBidWRnZXQgb2YgJDIuNSBtaWxsaW9uLiBIb3dldmVyLCBhY2Nlc3NpYmlsaXR5IGlzIGxvd2VyIHRoYW4gdGhhdCBvZiB0aGUgb3RoZXIgcGxhbm5pbmcgYXJlYXMgYXMgdGhlcmUgYXJlIGN1cnJlbnRseSBvbmx5IDEgTVJUIHN0YXRpb25zIHdpdGggMiBzdGF0aW9ucyBwbGFubmVkLiBEZXNwaXRlIHRoaXMsIGl0IHNob3VsZCBzdGlsbCBiZSByZWxhdGl2ZWx5IGFjY2Vzc2libGUgYXMgaXQgaXMgdmVyeSBjbG9zZSB0byB0aGUgQ0JELgoKSWYgd2UgYXJlIGxvb2tpbmcgdG8gYnV5IGEgZmxhdCB0byBsaXZlIGluLCB3ZSB3aWxsIGJlIG1vcmUgY29uY2VybmVkIGFib3V0IHRoZSBhY2Nlc3NpYmlsaXR5IGFzIHdlbGwgYXMgdmFsdWUgZm9yIG1vbmV5LiBJbiB0aGlzIHJlZ2FyZCwgd2Ugd291bGQgcHJlZmVyIHBsYW5uaW5nIGFyZWFzIGxpa2UgQmVkb2sgb3IgVG9hIFBheW9oIGFzIHRoZXkgYXJlIG1hdHVyZSBlc3RhdGUgd2l0aCByZWxhdGl2ZWx5IGhpZ2ggbGV2ZWxzIG9mIGFjY2Vzc2liaWxpdHkgYW5kIGFtZW5pdGllcy4gVG9hIFBheW9oIGhhcyBhbiBpbnRlZ3JhdGVkIHRyYW5zcG9ydCBodWIgd2hpbGUgQmVkb2sgaGFzIG1hbnkgdXBjb21pbmcgZnV0dXJlIE1SVCBzdGF0aW9ucyBicmluZ2luZyB0aGUgdG90YWwgdG8gMTEgc3RhdGlvbnMuIEluIGFkZGl0aW9uIHRoZSBhcGFydG1lbnRzIGluIHRoZXNlIGFyZWFzIGFyZSBtb3JlIGFmZm9yZGFibGUgYW5kIHdlIGNvdWxkIGV2ZW4gcHVyY2hhc2UgMiBpZiB3ZSB3YW50ZWQgdG8uIAoKQWx0ZXJuYXRpdmVseSwgaWYgd2UgYXJlIHNpbXBseSBsb29raW5nIGZvciBhIGxhcmdlciBzcGFjZSBhdCB0aGUgY2hlYXBlc3QgcG9zc2libGUgcmF0ZXMgYW5kIHNhdmUgdGhlIHJlc3Qgb2YgdGhlIG1vbmV5IGZvciBvdGhlciB1c2VzLCB0aGUgYmVzdCBvcHRpb24gd291bGQgYmUgSnVyb25nIEVhc3Qgb3IgSnVyb25nIFdlc3QgYXMgdGhleSBoYXZlIG9uZSBvZiB0aGUgbG93ZXN0IHBzbS4gSW4gYWRkaXRpb24sIGl0IGlzIGFsc28gYSBtYXR1cmUgZXN0YXRlIHdpdGggbXVsdGlwbGUgbWFsbHMgYW5kIGRlY2VudCBhY2Nlc3NpYmlsaXR5LiBUaGVyZSB3aWxsIGJlIGEgdG90YWwgb2YgMTUgTVJUIHN0YXRpb25zIGluIHRoZSBhcmVhIGluIHRoZSBuZWFyIGZ1dHVyZS4KCgoKCgoK